#include "znx_http_conf.h"
#include "comm/znx_crc.h"


static znx_bool_t
znx_http_site_server_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_site_proxy_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_site_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_sites_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_site_server_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_site_proxy_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_site_locations_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_site_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_addr_site_address_names_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);
static znx_bool_t
znx_http_loc_conf_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err);



static znx_command_t znx_http_site_commands[] = {
    {
        ZNX_STRING("version"),
        znx_command_set_int64,
        offsetof(znx_http_site_conf_t, version)
    },

    {
        ZNX_STRING("server_core_conf"),
        znx_http_site_server_core_conf_parse,
        0
    },

    {
        ZNX_STRING("proxy_core_conf"),
        znx_http_site_proxy_core_conf_parse,
        0
    },

    {
        ZNX_STRING("addr_sites"),
        znx_http_addr_sites_parse,
        0
    },

    {
        ZNX_STRING("modules"),
        znx_http_site_modules_conf_parse,
        0
    },

    znx_null_command
};


static znx_command_t znx_http_addr_site_commands[] = {
    {
        ZNX_STRING("address_names"),
        znx_http_addr_site_address_names_parse,
        0
    },

    {
        ZNX_STRING("server_core_conf"),
        znx_http_addr_site_server_core_conf_parse,
        0
    },

    {
        ZNX_STRING("proxy_core_conf"),
        znx_http_addr_site_proxy_core_conf_parse,
        0
    },

    {
        ZNX_STRING("locations"),
        znx_http_addr_site_locations_conf_parse,
        0
    },

    {
        ZNX_STRING("modules"),
        znx_http_addr_site_modules_conf_parse,
        0
    },

    znx_null_command
};


static znx_command_t znx_http_loc_conf_commands[] = {
    {
        ZNX_STRING("type"),
        znx_command_set_int64,
        offsetof(znx_http_loc_conf_t, type)
    },

    {
        ZNX_STRING("path"),
        znx_command_set_string,
        offsetof(znx_http_loc_conf_t, path)
    },

    {
        ZNX_STRING("modules"),
        znx_http_loc_conf_modules_conf_parse,
        0
    },

    znx_null_command
};


static znx_bool_t
znx_http_site_server_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "server_core_conf type is not mapping node");
        return ZNX_FALSE;
    }

    // create server_core_conf
    znx_http_site_conf_t    *site_conf = conf;
    site_conf->server_core_conf = znx_http_core_conf_create(pool);
    return znx_yaml_map_node_parser(
        site_conf->server_core_conf, pool,
        doc, node, znx_http_core_commands, err);
}

static znx_bool_t
znx_http_site_proxy_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "proxy_core_conf type is not mapping node");
        return ZNX_FALSE;
    }

    // create proxy_core_conf
    znx_http_site_conf_t    *site_conf = conf;
    site_conf->proxy_core_conf = znx_http_core_conf_create(pool);
    return znx_yaml_map_node_parser(
        site_conf->proxy_core_conf, pool,
        doc, node, znx_http_core_commands, err);
}


static znx_bool_t
znx_http_site_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "http_site modules type is not mapping node");
        return ZNX_FALSE;
    }

    yaml_node_pair_t            *pair;
    yaml_node_t                 *key;
    yaml_node_t                 *value;
    size_t                      err_buf_len = command->name.len + 128;

    for (pair = node->data.mapping.pairs.start;
        pair < node->data.mapping.pairs.top;
        pair++)
    {
        key = yaml_document_get_node(doc, pair->key);
        value = yaml_document_get_node(doc, pair->value);

        if (key->type != YAML_SCALAR_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v element key node type must is YAML_SCALAR_NODE",
                &command->name) - err->data);
            return ZNX_FALSE;
        }

        for (int i = 0; i < http_modules_num; i++) {
            if (key->data.scalar.length == http_modules[i]->name.len &&
                znx_strncmp(key->data.scalar.value,
                    http_modules[i]->name.data,
                    http_modules[i]->name.len) == 0)
            {
                if (http_modules[i]->site_conf_parse) {
                    // it's znx_http_site_conf.
                    if (!http_modules[i]->site_conf_parse(conf,
                        pool, doc, value, err))
                    {
                        return ZNX_FALSE;
                    }
                }

                break;
            }
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_addr_sites_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_SEQUENCE_NODE) {
        ZNX_STR_SET(err, "addr_sites type is not sequence node");
        return ZNX_FALSE;
    }

    size_t                      err_buf_len;
    znx_http_site_conf_t        *site_conf = conf;
    znx_http_addr_site_conf_t   *addr_site;

    // create addr_sites array.
    site_conf->addr_sites = znx_array_create(2, sizeof(znx_http_addr_site_conf_t));
    znx_pool_add_cleanup(site_conf->pool,
        znx_array_destroy_handle, site_conf->addr_sites);

    err_buf_len = command->name.len + 128;

    yaml_node_item_t        *item;
    yaml_node_t             *value_node;

    for (item = node->data.sequence.items.start;
        item < node->data.sequence.items.top;
        item++)
    {
        value_node = yaml_document_get_node(doc, *item);
        if (value_node->type != YAML_MAPPING_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v found no maping node element", &command->name) - err->data);
            return ZNX_FALSE;
        }

        addr_site = znx_array_push(site_conf->addr_sites);
        bzero(addr_site, sizeof(znx_http_addr_site_conf_t));
        addr_site->site_conf = site_conf;
        addr_site->a_modules = znx_pool_calloc(pool,
            sizeof(void *) * (size_t)http_modules_num);
        if (!znx_yaml_map_node_parser(addr_site, pool, doc,
            value_node, znx_http_addr_site_commands, err))
        {
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_addr_site_address_names_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    znx_http_addr_site_conf_t   *addr_site = conf;
    znx_http_site_conf_t        *site_conf = addr_site->site_conf;

    size_t                      err_buf_len;
    znx_str_t                   *strp;
    znx_address_t               *address;

    addr_site->address_names = znx_array_create(2, sizeof(znx_str_t));
    znx_pool_add_cleanup(site_conf->pool,
        znx_array_destroy_handle, addr_site->address_names);
    addr_site->address_arr = znx_array_create(2, sizeof(znx_address_t));
    znx_pool_add_cleanup(site_conf->pool,
        znx_array_destroy_handle, addr_site->address_arr);
    err_buf_len = command->name.len + 128;

    yaml_node_item_t            *item;
    yaml_node_t                 *value_node;

    for (item = node->data.sequence.items.start;
        item < node->data.sequence.items.top;
        item++)
    {
        value_node = yaml_document_get_node(doc, *item);
        if (value_node->type != YAML_SCALAR_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v found no scalar node element", &command->name) - err->data);
            return ZNX_FALSE;
        }

        strp = znx_array_push(addr_site->address_names);
        strp->len = value_node->data.scalar.length;
        strp->data = znx_pool_malloc(pool, strp->len + 1);
        memcpy(strp->data, value_node->data.scalar.value, strp->len);
        strp->data[strp->len] = '\0';

        address = znx_array_push(addr_site->address_arr);

        int         port = 0;
        znx_bool_t  ret;
        ret = znx_address_parser(strp->data, strp->len, address, &port);

        if (!ret || port <= 0) {
            err_buf_len = strp->len + 128;
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "parse %v failed, or not found address port", strp) - err->data);
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_addr_site_server_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "server_core_conf type is not mapping node");
        return ZNX_FALSE;
    }

    // create server_core_conf
    znx_http_addr_site_conf_t   *addr_site = conf;
    addr_site->server_core_conf = znx_http_core_conf_create(pool);
    return znx_yaml_map_node_parser(
        addr_site->server_core_conf, pool,
        doc, node, znx_http_core_commands, err);
}


static znx_bool_t
znx_http_addr_site_proxy_core_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "server_core_conf type is not mapping node");
        return ZNX_FALSE;
    }

    // create server_core_conf
    znx_http_addr_site_conf_t   *addr_site = conf;
    addr_site->proxy_core_conf = znx_http_core_conf_create(pool);
    return znx_yaml_map_node_parser(
        addr_site->proxy_core_conf, pool,
        doc, node, znx_http_core_commands, err);
}


static znx_bool_t
znx_http_addr_site_locations_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_SEQUENCE_NODE) {
        ZNX_STR_SET(err, "addr_sites type is not sequence node");
        return ZNX_FALSE;
    }

    size_t                      err_buf_len;
    znx_http_addr_site_conf_t   *addr_site = conf;
    znx_http_site_conf_t        *site_conf = addr_site->site_conf;
    znx_http_loc_conf_t         *loc_conf;

    // create location conf
    addr_site->loc_confs = znx_array_create(2, sizeof(znx_http_loc_conf_t));
    znx_pool_add_cleanup(site_conf->pool,
        znx_array_destroy_handle, addr_site->loc_confs);
    err_buf_len = command->name.len + 128;

    yaml_node_item_t            *item;
    yaml_node_t                 *value_node;

    for (item = node->data.sequence.items.start;
        item < node->data.sequence.items.top;
        item++)
    {
        value_node = yaml_document_get_node(doc, *item);
        if (value_node->type != YAML_MAPPING_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v found no maping node element", &command->name) - err->data);
            return ZNX_FALSE;
        }

        loc_conf = znx_array_push(addr_site->loc_confs);
        bzero(loc_conf, sizeof(znx_http_loc_conf_t));
        loc_conf->addr_site = addr_site;
        loc_conf->l_modules = znx_pool_calloc(pool,
            sizeof(void *) * (size_t)http_modules_num);

        if (!znx_yaml_map_node_parser(loc_conf, pool, doc,
            value_node, znx_http_loc_conf_commands, err))
        {
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_addr_site_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "http_addr_site modules type is not mapping node");
        return ZNX_FALSE;
    }

    yaml_node_pair_t            *pair;
    yaml_node_t                 *key;
    yaml_node_t                 *value;
    size_t                      err_buf_len = command->name.len + 128;

    for (pair = node->data.mapping.pairs.start;
        pair < node->data.mapping.pairs.top;
        pair++)
    {
        key = yaml_document_get_node(doc, pair->key);
        value = yaml_document_get_node(doc, pair->value);

        if (key->type != YAML_SCALAR_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v element key node type must is YAML_SCALAR_NODE",
                &command->name) - err->data);
            return ZNX_FALSE;
        }

        for (int i = 0; i < http_modules_num; i++) {
            if (key->data.scalar.length == http_modules[i]->name.len &&
                znx_strncmp(key->data.scalar.value,
                    http_modules[i]->name.data,
                    http_modules[i]->name.len) == 0)
            {
                if (http_modules[i]->addr_site_parse) {
                    // it's znx_http_addr_site_conf.
                    if (!http_modules[i]->addr_site_parse(conf,
                        pool, doc, value, err))
                    {
                        return ZNX_FALSE;
                    }
                }

                break;
            }
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_loc_conf_modules_conf_parse(void *conf,
    znx_pool_t *pool, znx_command_t *command,
    yaml_document_t *doc, yaml_node_t *node, znx_str_t *err)
{
    if (node->type != YAML_MAPPING_NODE) {
        ZNX_STR_SET(err, "http_loc modules type is not mapping node");
        return ZNX_FALSE;
    }

    yaml_node_pair_t            *pair;
    yaml_node_t                 *key;
    yaml_node_t                 *value;
    size_t                      err_buf_len = command->name.len + 128;

    for (pair = node->data.mapping.pairs.start;
        pair < node->data.mapping.pairs.top;
        pair++)
    {
        key = yaml_document_get_node(doc, pair->key);
        value = yaml_document_get_node(doc, pair->value);

        if (key->type != YAML_SCALAR_NODE) {
            err->data = znx_pool_malloc(pool, err_buf_len);
            err->len = (size_t)(znx_snprintf(err->data, err_buf_len,
                "command %v element key node type must is YAML_SCALAR_NODE",
                &command->name) - err->data);
            return ZNX_FALSE;
        }

        for (int i = 0; i < http_modules_num; i++) {
            if (key->data.scalar.length == http_modules[i]->name.len &&
                znx_strncmp(key->data.scalar.value,
                    http_modules[i]->name.data,
                    http_modules[i]->name.len) == 0)
            {
                if (http_modules[i]->loc_conf_parse) {
                    // it's znx_http_loc_conf.
                    if (!http_modules[i]->loc_conf_parse(conf,
                        pool, doc, value, err))
                    {
                        return ZNX_FALSE;
                    }
                }

                break;
            }
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_loc_conf_check_and_fix(znx_http_loc_conf_t *loc_conf)
{
    znx_http_addr_site_conf_t           *addr_site = loc_conf->addr_site;
    znx_http_site_conf_t                *site_conf = addr_site->site_conf;

    if (loc_conf->type <= 0) {
        loc_conf->type = ZNX_HTTP_PERFIX_MATCH_PATH;
    }

    if (loc_conf->type > ZNX_HTTP_REGEX_MATCH_PATH ||
        loc_conf->type < ZNX_HTTP_PERFIX_MATCH_PATH)
    {
        ZNX_STR_SET(&site_conf->err, "invalid loc_conf type");
        return ZNX_FALSE;
    }

    if (loc_conf->path.len == 0) {
        ZNX_STR_SET(&site_conf->err, "invalid loc_conf path");
        return ZNX_FALSE;
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_addr_site_check_and_fix(znx_http_addr_site_conf_t *addr_site)
{
    znx_http_site_conf_t    *site_conf = addr_site->site_conf;

    if (addr_site->server_core_conf == NULL) {
        addr_site->server_core_conf = site_conf->server_core_conf;
    } else {
        if (!znx_http_core_conf_merger_and_fix(
            addr_site->server_core_conf,
            site_conf->server_core_conf))
        {
            return ZNX_FALSE;
        }
    }

    if (addr_site->proxy_core_conf == NULL) {
        addr_site->proxy_core_conf = site_conf->proxy_core_conf;
    } else {
        if (!znx_http_core_conf_merger_and_fix(
            addr_site->proxy_core_conf,
            site_conf->proxy_core_conf))
        {
            return ZNX_FALSE;
        }
    }

    if (addr_site->loc_confs == NULL || addr_site->loc_confs->nelts == 0) {
        ZNX_STR_SET(&site_conf->err, "not found location conf");
        return ZNX_FALSE;
    }

    znx_http_loc_conf_t         *loc_confp, *loc_conf;
    loc_confp = addr_site->loc_confs->elts;
    for (size_t i = 0; i < addr_site->loc_confs->nelts; i++) {
        loc_conf = loc_confp + i;
        if (!znx_http_loc_conf_check_and_fix(loc_conf)) {
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}


static znx_bool_t
znx_http_site_conf_check_and_fix(znx_http_site_conf_t *site_conf)
{
    if (site_conf->server_core_conf == NULL) {
        site_conf->server_core_conf = g_http_main_conf->server_core_conf;
    } else {
        if (!znx_http_core_conf_merger_and_fix(
            site_conf->server_core_conf,
            g_http_main_conf->server_core_conf))
        {
            return ZNX_FALSE;
        }
    }

    if (site_conf->proxy_core_conf == NULL) {
        site_conf->proxy_core_conf = g_http_main_conf->proxy_core_conf;
    } else {
        if (!znx_http_core_conf_merger_and_fix(
            site_conf->proxy_core_conf,
            g_http_main_conf->proxy_core_conf))
        {
            return ZNX_FALSE;
        }
    }

    if (site_conf->addr_sites == NULL || site_conf->addr_sites->nelts == 0) {
        ZNX_STR_SET(&site_conf->err, "not found addr_sites");
        return ZNX_FALSE;
    }

    znx_http_addr_site_conf_t   *addr_sitep, *addr_site;
    addr_sitep = site_conf->addr_sites->elts;
    for (size_t i = 0; i < site_conf->addr_sites->nelts; i++) {
        addr_site = addr_sitep + i;
        if (!znx_http_addr_site_check_and_fix(addr_site)) {
            return ZNX_FALSE;
        }
    }

    return ZNX_TRUE;
}


znx_bool_t
znx_http_site_conf_parse(znx_http_site_conf_t *site_conf,
    const char *filepath, znx_str_t *server_name)
{
    site_conf->crc = znx_crc32_long(server_name->data, server_name->len);
    site_conf->server_name.data = znx_pool_pstr(site_conf->pool, server_name);
    site_conf->server_name.len = server_name->len;

    if (!znx_yaml_map_conf_parser(site_conf,
        site_conf->pool, filepath,
        znx_http_site_commands, &site_conf->err))
    {
        return ZNX_FALSE;
    }

    if (!znx_http_site_conf_check_and_fix(site_conf)) {
        return ZNX_FALSE;
    }

    return znx_http_module_site_conf_post_exec(site_conf);
}